#include <stdlib.h>
#include <stdint.h>
#include <stddef.h>
#include <unistd.h>

#include <system/imp_alloc.h>
#include <string.h>
#include <imp/imp_common.h>
#include <imp/imp_log.h>

#include "Jz_mempool_continuous.h"
#include "Jz_mempool.h"

#define POOL_MAX 32

#define TAG "JZ_MEMPOOL"

#define container_of(ptr, type, member) ({			\
	const typeof( ((type *)0)->member ) *__mptr = (ptr);	\
	(type *)( (char *)__mptr - offsetof(type,member) );})

extern uint32_t alloc_kmem_get_paddr(uint32_t vaddr);
extern uint32_t alloc_kmem_get_vaddr(uint32_t paddr);
extern int alloc_kmem_flush_cache(void *vaddr, size_t size, uint32_t dir);

typedef struct
{
	mempool_t pool;
	struct MEMPoolAlloc poolalloc;
	void *allocHandler;
} Jz_mempool_t;

//TODO collector, hashmap?
typedef struct {
	Jz_mempool_t *pools[POOL_MAX];
} Jz_pool_Manager_t;

static Jz_pool_Manager_t g_manager = {0};

static Jz_pool_Manager_t *manager()
{
	return &g_manager;
}

/* alloc manager interface */
static int alloc_add_info(PoolAllocInfo *head, PoolAllocInfo *info)
{
	PoolAllocInfo *tmp = head;

	while(tmp->n_info != NULL)
		tmp = tmp->n_info;

	tmp->n_info = info;
	return 0;
}

static int alloc_del_info(PoolAllocInfo *head, uint32_t addr)
{
	PoolAllocInfo *tmp_p = head;
	PoolAllocInfo *tmp_n = head;

	if(head->n_info != NULL) {
		tmp_n = tmp_p->n_info;
	}

	while(1) {
		if(tmp_n->vaddr == addr) {
			tmp_p->n_info = tmp_n->n_info;
			free(tmp_n);
			break;
		}

		if(tmp_n->n_info != NULL) {
			tmp_p = tmp_p->n_info;
			tmp_n = tmp_n->n_info;
		} else {
			break;
		}
	}
	return 0;
}

#if 0
static PoolAllocInfo *alloc_get_info(MEMPoolAlloc *alloc, void *addr)
{
	PoolAllocInfo *tmp = &alloc->info;

	while(tmp != NULL) {
		if(tmp->vaddr == (uint32_t)addr)
			break;
		tmp = tmp->n_info;
	}

	return tmp;
}

static int alloc_dump_info(PoolAllocInfo *head)
{
	PoolAllocInfo *tmp = head;

	while(tmp != NULL) {
		IMP_LOG_INFO(TAG, "\ninfo->n_info = %p\n", tmp->n_info);
		IMP_LOG_INFO(TAG, "info->owner = %s\n", tmp->owner);
		IMP_LOG_INFO(TAG, "info->vaddr = 0x%08x\n", tmp->vaddr);
		IMP_LOG_INFO(TAG, "info->paddr = 0x%08x\n", tmp->paddr);
		IMP_LOG_INFO(TAG, "info->length = %d\n", tmp->length);
		IMP_LOG_INFO(TAG, "info->ref_cnt = %d\n", tmp->ref_cnt);
		IMP_LOG_INFO(TAG, "info->mem_attr = %d\n", tmp->mem_attr);
		tmp = tmp->n_info;
	}

	return 0;
}
#endif
static int alloc_dump_to_file(PoolAllocInfo *head, const char *dumpFile)
{

	if(access(dumpFile, F_OK) <0) {
		return 0;
	}
	PoolAllocInfo *tmp = head;
	FILE *fd = fopen(dumpFile, "w");
	if(fd == NULL) {
		IMP_LOG_ERR(TAG, "Open %s failed\n", dumpFile);
		return -1;
	}

	while(tmp != NULL) {
		char buf[512] = "";
		int len = 0;
		len = sprintf(buf, "\ninfo->n_info = %p\n", tmp->n_info);
		len += sprintf(buf+len, "info->owner = %s\n", tmp->owner);
		len += sprintf(buf+len, "info->vaddr = 0x%08x\n", tmp->vaddr);
		len += sprintf(buf+len, "info->paddr = 0x%08x\n", tmp->paddr);
		len += sprintf(buf+len, "info->length = %d\n", tmp->length);
		len += sprintf(buf+len, "info->ref_cnt = %d\n", tmp->ref_cnt);
		len += sprintf(buf+len, "info->mem_attr = %d\n", tmp->mem_attr);
		tmp = tmp->n_info;

		fwrite(buf, len, 1, fd);
	}

	fclose(fd);

	return 0;
}

static Jz_mempool_t *get_Jz_mempool(int poolId)
{
	Jz_pool_Manager_t *mng = manager();
	if(poolId < 0 && poolId >= POOL_MAX) {
		fprintf(stderr, "poolId error, poolId: %d\n", poolId);
		return NULL;
	}

	Jz_mempool_t *jzmp = mng->pools[poolId];
	if(!jzmp) {
		fprintf(stderr, "should request poolId, poolId: %d\n", poolId);
		return NULL;
	}

	return mng->pools[poolId];
}


Jz_mempool_t* request_mempool(int requestId, int size, char *name)
{
	Jz_pool_Manager_t *mng = manager();
	if(requestId < 0 && requestId >= POOL_MAX) {
		fprintf(stderr, "requestId error, requestId: %d\n", requestId);
		return NULL;
	}
	if(mng->pools[requestId]) {
		fprintf(stderr, "already request\n");
		return NULL;
	}

	Jz_mempool_t *jzmp = (Jz_mempool_t *)malloc(sizeof(Jz_mempool_t));
	if(!jzmp) {
		fprintf(stderr, "malloc error: %d\n", __LINE__);
		return NULL;
	}

	memset(jzmp, 0, sizeof(Jz_mempool_t));
	mempool_t *pool = &jzmp->pool;

	pool->poolId = requestId;
	pool->size = size;
	pool->pAlloc = (IMPAlloc *)malloc(sizeof(IMPAlloc));
	if(!pool->pAlloc) {
		fprintf(stderr, "malloc error: %d\n", __LINE__);
		goto ERR_ALLOC;
	}

	memset(pool->pAlloc, 0, sizeof(IMPAlloc));
	if (IMP_Alloc(pool->pAlloc, size, name) < 0) {
		fprintf(stderr, "IMP Alloc error\n");
		goto ERR_IMP_ALLOC;
	}

	pool->virBase = pool->pAlloc->info.vaddr;
	pool->phyBase = pool->pAlloc->info.paddr;
	mng->pools[requestId] = jzmp;

	return jzmp;
ERR_IMP_ALLOC:
	free(pool->pAlloc);
ERR_ALLOC:
	free(jzmp);
	return NULL;
}

int release_mempool(Jz_mempool_t *mpool)
{
	if(!mpool)
		return 0;

	mempool_t *mp = &mpool->pool;

	if(mp->poolId < 0 && mp->poolId > POOL_MAX)
		manager()->pools[mp->poolId] = NULL;

	mp->CodecAllocatorDeinitCB(mp->CodecAllocator);
	free(mpool);

	return 0;

}

mempool_t* get_mempool(int poolId)
{
	Jz_mempool_t *jzmp = get_Jz_mempool(poolId);
	if(jzmp)
		return &jzmp->pool;
	return NULL;
}

static void* pool_alloc_mem(MEMPoolAlloc *alloc, int size, char *owner)
{

	if(!alloc)
		return NULL;
	Jz_mempool_t *jzmp = container_of(alloc, Jz_mempool_t, poolalloc);
	PoolMemAlloc *memmng = &jzmp->poolalloc.mem_manager;
	if(!memmng)
		return NULL;

	return memmng->memalloc(jzmp->allocHandler, size, owner);
}

static void pool_free_mem(MEMPoolAlloc *alloc, void *ptr)
{
	if(!alloc)
		return;
	Jz_mempool_t *jzmp = container_of(alloc, Jz_mempool_t, poolalloc);
	PoolMemAlloc *memmng = &jzmp->poolalloc.mem_manager;
	if(!memmng)
		return;
	memmng->memfree(jzmp->allocHandler, ptr);

	if(alloc_del_info(&alloc->info, (uint32_t)ptr)) {
		IMP_LOG_ERR(TAG, "alloc del info failed\n");
	}
}

static int dumpMemPoolToFile(MEMPoolAlloc *alloc)
{
	if(!alloc)
		return -1;
	Jz_mempool_t *jzmp = container_of(alloc, Jz_mempool_t, poolalloc);
	PoolMemAlloc *memmng = &jzmp->poolalloc.mem_manager;
	if(!memmng)
		return -1;

	if(memmng->dump_to_file) {
		memmng->dump_to_file(jzmp->allocHandler);
	}

	char fileName[64] = "/tmp/mempool_manager_pool";
	sprintf(fileName+strlen(fileName), "%d", jzmp->pool.poolId);

	alloc_dump_to_file(&alloc->info, fileName);
	return 0;
}

int IMP_MemPool_InitPool(int poolId, size_t size, char *name)
{
	Jz_mempool_t *mpool = NULL;
	if(!(mpool = request_mempool(poolId, size, name))) {
		return -1;
	}

	MEMPoolAlloc *alloc = &mpool->poolalloc;
	alloc->alloc_mem = pool_alloc_mem;
	alloc->free_mem = pool_free_mem;

	alloc->dump_mem_to_file = dumpMemPoolToFile;

#if 0
	alloc->sp_alloc_mem = spAllocMem;
	alloc->getAllocInfo = alloc_get_info;
	alloc->get_mem_attr = getMemAttr;
	alloc->set_mem_attr = setMemAttr;
	alloc->dump_mem_status = dumpMemStatus;
#endif
	//continus
	alloc->mem_manager.init = mempool_continuous_init;
	alloc->mem_manager.memalloc = mempool_continuous_alloc;
	alloc->mem_manager.memfree = mempool_continuous_free;
	alloc->mem_manager.dump = mempool_continuous_dump;
	alloc->mem_manager.dump_to_file = mempool_continuous_dump_to_file;

	alloc->mem_alloc.get_paddr = alloc_kmem_get_paddr;
	alloc->mem_alloc.get_vaddr = alloc_kmem_get_vaddr;
	alloc->mem_alloc.flush_cache = alloc_kmem_flush_cache;

	mpool->allocHandler = alloc->mem_manager.init(mpool->pool.virBase, mpool->pool.size, name);
	if(!(mpool->allocHandler)) {
		release_mempool(mpool);
		return -1;
	}

	return 0;
}

mempool_t* IMP_MemPool_GetById(int poolId)
{
	return get_mempool(poolId);
}

int IMP_PoolAlloc(int poolId, IMPAlloc *alloc, int size, char *owner)
{

	Jz_mempool_t *jzmp = get_Jz_mempool(poolId);
	if(!jzmp) {
		IMP_LOG_ERR(TAG, "get Jz mempool error\n");
		return -1;
	}

	IMP_LOG_DBG(TAG, "poolId: %d================> %s, %d, owner: %s, size: %d\n",
		   poolId, __FUNCTION__, __LINE__, owner, size);

	PoolAllocInfo *info = NULL;
	MEMPoolAlloc *mpalloc = &jzmp->poolalloc;
	void *addr = NULL;
	if(mpalloc->alloc_mem) {
		addr = mpalloc->alloc_mem(&jzmp->poolalloc, size, owner);
		if(addr == NULL) {
			IMP_LOG_ERR(TAG, "g_alloc.alloc_mem failed\n");
//			IMP_Alloc_Dump();
			return -1;
		}

		info = (PoolAllocInfo *)calloc(1, sizeof(PoolAllocInfo));
		if(!info) {
			IMP_LOG_ERR(TAG, "Alloc Info struct malloc failed\n");
			goto ERR_ALLOC_INFO;
		}

		info->n_info = NULL;
		strncpy(info->owner, owner, sizeof(info->owner) - 1);
		info->vaddr = (uint32_t)addr;
		info->length = size;
		info->ref_cnt = 1;

		if(mpalloc->mem_alloc.get_paddr) {
			info->paddr = mpalloc->mem_alloc.get_paddr(info->vaddr);
		} else {
			IMP_LOG_ERR(TAG, "%s mem_alloc->get_paddr is NULL\n", __func__);
			goto ERR_GET_PADDR;
		}

		if(alloc_add_info(&mpalloc->info, info) != 0) {
			IMP_LOG_ERR(TAG, "%s alloc add info failed\n", __func__);
			goto ERR_GET_PADDR;
		}

		strncpy(alloc->info.owner, info->owner, sizeof(alloc->info.owner) - 1);
		alloc->info.vaddr = info->vaddr;
		alloc->info.paddr = info->paddr;
		alloc->info.length = info->length;
		alloc->info.ref_cnt = info->ref_cnt;
		alloc->info.mem_attr = info->mem_attr;

		IMP_PoolAlloc_Dump(poolId);
		return 0;
	}

	return -1;

ERR_GET_PADDR:
	if(info)
		free(info);
ERR_ALLOC_INFO:
	if(jzmp->poolalloc.free_mem)
		jzmp->poolalloc.free_mem(&jzmp->poolalloc, addr);

	return -1;
}

void IMP_PoolFree(int poolId, IMPAlloc *alloc, void *ptr)
{
	if(alloc == NULL || ptr == NULL) {
		IMP_LOG_ERR(TAG, "IMPAlloc *alloc == NULL or ptr == NULL\n");
		return;
	}

	Jz_mempool_t *jzmp = get_Jz_mempool(poolId);
	if(!jzmp) {
		IMP_LOG_ERR(TAG, "get Jz mempool error\n");
		return;
	}

	IMP_LOG_DBG(TAG, "poolId: %d================> %s, %d, owner: %s, size: %d\n",
		   poolId, __FUNCTION__, __LINE__, alloc->info.owner, alloc->info.length);
	MEMPoolAlloc *mpalloc = &jzmp->poolalloc;
	if(mpalloc->free_mem) {
		mpalloc->free_mem(mpalloc, ptr);
		alloc->info.ref_cnt -= 1;
		if(alloc->info.ref_cnt == 0) {
			alloc->info.vaddr = 0;
			alloc->info.paddr = 0;
		}
	}

	IMP_PoolAlloc_Dump(poolId);

}

IMPMemAttr IMP_PoolAlloc_Get_Attr(int poolId, IMPAlloc *alloc)
{
	return 0;
}

void *IMP_PoolVirt_to_Phys(int poolId, void *vaddr)
{
	return NULL;
}

void *IMP_PoolPhys_to_Virt(int poolId, void *paddr)
{
	return NULL;
}

int IMP_PoolAlloc_Set_Attr(int poolId, IMPAlloc *alloc)
{
	return 0;
}

void IMP_PoolAlloc_Dump(int poolId)
{
	Jz_mempool_t *jzmp = get_Jz_mempool(poolId);
	if(!jzmp) {
		IMP_LOG_ERR(TAG, "get jz mempool error: %d\n", poolId);
		return;
	}
	MEMPoolAlloc *mpalloc = &jzmp->poolalloc;
	if(mpalloc->dump_mem_to_file) {
		mpalloc->dump_mem_to_file(mpalloc);
	}
}

int IMP_PoolFlushCache(int poolId, void *vaddr, size_t size, uint32_t dir)
{
	return 0;
}
